﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

// copied from : http://stackoverflow.com/questions/2756151/dependency-injection-for-windows-phone-7
// will be dropped as soon as a real IoC framework is available

namespace JustinAngel.Neurons.Framework
{
    public class DuplicateRegistrationException : Exception
    {
        public DuplicateRegistrationException() { }
        public DuplicateRegistrationException(string message) : base(message) { }
        public DuplicateRegistrationException(string message, Exception inner) : base(message, inner) { }
    }

    public interface IDIContainer
    {
        void Register<TIntf, TClass>()
            where TIntf : class
            where TClass : TIntf;
        TIntf Resolve<TIntf>() where TIntf : class;
        void RegisterInstance<TIntf>(TIntf instance);
        void RegisterInstance(Type type, object instance);
        void BuildUp<T>(T t);

        void Register(Type from, Type to);

        object Resolve(Type type);
        bool DoesRegistrationExist(Type type);
    }

    public class DIContainer : IDIContainer
    {

        Dictionary<Type, Type> m_TypeRegistrations;
        Dictionary<Type, object> m_InstanceRegistrations;

        public DIContainer()
        {
            m_TypeRegistrations = new Dictionary<Type, Type>();
            m_InstanceRegistrations = new Dictionary<Type, object>();
        }

        public void Register<TIntf, TClass>()
            where TIntf : class
            where TClass : TIntf
        {
            if (DoesRegistrationExist<TIntf>())
                throw new DuplicateRegistrationException("Can only contain one registration per type");
            m_TypeRegistrations.Add(typeof(TIntf), typeof(TClass));
        }

        public TIntf Resolve<TIntf>() where TIntf : class
        {
            return Resolve(typeof(TIntf)) as TIntf;
        }

        public object Resolve(Type type)
        {
            if (!m_TypeRegistrations.ContainsKey(type) && type.IsClass)
            {
                m_TypeRegistrations.Add(type, type);
            }

            if (!m_TypeRegistrations.ContainsKey(type))
            {
                if (!m_InstanceRegistrations.ContainsKey(type))
                    return null;
                //    throw new NotSupportedException("Cannot find registration for type " + type.FullName + ".");
                else
                    return m_InstanceRegistrations[type];
            }
            else
            {
                var createdType = m_TypeRegistrations[type];

                ConstructorInfo[] constructors = createdType.GetConstructors();
                ConstructorInfo mostSpecificConstructor = null;
                foreach (var c in constructors)
                {
                    if (mostSpecificConstructor == null || mostSpecificConstructor.GetParameters().Length < c.GetParameters().Length)
                    {
                        mostSpecificConstructor = c;
                    }
                }

                List<object> constructorParameters = new List<object>();
                foreach (var a in mostSpecificConstructor.GetParameters())
                {
                    constructorParameters.Add(Resolve(a.ParameterType));
                }

                var ReturnValue = Activator.CreateInstance(createdType, constructorParameters.ToArray());
                BuildUp(createdType, ReturnValue);
                return ReturnValue;

            }


        }

        private bool DoesRegistrationExist<T>()
        {
            return DoesRegistrationExist(typeof (T));
        }

        public bool DoesRegistrationExist(Type type)
        {
            return m_InstanceRegistrations.ContainsKey(type) || m_TypeRegistrations.ContainsKey(type);
        }

        public void RegisterInstance<TIntf>(TIntf instance)
        {
            if (DoesRegistrationExist<TIntf>())
            {
                throw new DuplicateRegistrationException("Can only contain one registration per type");
            }
            m_InstanceRegistrations.Add(typeof(TIntf), instance);
        }

        public void RegisterInstance(Type type, object instance)
        {
            if (DoesRegistrationExist(type))
            {
                throw new DuplicateRegistrationException("Can only contain one registration per type");
            }
            m_InstanceRegistrations.Add(type, instance);
        }

        public void BuildUp<T>(T t)
        {
            BuildUp(typeof(T), t);
        }

        public void Register(Type from, Type to)
        {
            if (DoesRegistrationExist(from))
                throw new DuplicateRegistrationException("Can only contain one registration per type");
            m_TypeRegistrations.Add(from, to);
        }

        public void BuildUp(Type type, object t)
        {
            Type createdType = type;
            var properties = createdType.GetProperties().Where(p => p.GetCustomAttributes(typeof(DependencyAttribute), true).Any());
            foreach (var p in properties)
            {
                p.SetValue(t, Resolve(p.PropertyType), null);
            }
        }

    }


    [AttributeUsage(AttributeTargets.Property)]
    public class DependencyAttribute : Attribute
    {

    }
}
